package controllers

import (
	"encoding/base64"
	"encoding/json"
	"github.com/astaxie/beego"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"io/ioutil"
	"nft-server/common"
	"nft-server/db"
	"nft-server/errcode"
	"nft-server/logger"
	"nft-server/service"
	"nft-server/util"
	"path/filepath"
	"strings"
	"time"
)

const (
	LoginURI              = "/v1/user/login"
	LoginUsingPhoneNumURI = "/v1/user/loginbyphone"
	RegistrationURI       = "/v1/user/registration"
	LogoutURI             = "/v1/user/logout"
	CaptchaURI            = "/v1/user/captcha"
	ProfileURI            = "/v1/user/profile"
	UnsubscribeURI        = "/v1/user/unsubscribe"
	ChangepassURI         = "/v1/user/changepass"
	CheckUserNameURI      = "v1/user/checkusername"
	CheckUserPhoneRegURI  = "v1/user/checkuserphoneused"
	AuthenticationURI     = "/v1/user/authentication"
	CheckAuthenticURI     = "/v1/user/checkauthentication"
	VerifyPhoneNumURI     = "/v1/user/verifyphonenum"
	UpdatePhoneNumURI     = "/v1/user/updatephonenum"
	ChangeProfilePhotoURI = "/v1/user/changeProfilePhoto"
	GetProfilePhotoURI    = "/v1/user/getProfilePhoto"
)

const (
	NameRege   = "^(?![0-9]+$)[0-9A-Za-z]{3,32}$"
	PassRege   = "^(?=.*\\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[~!@#$%^&*()_+{}|\":<>?`[\\];',.\\/])[\\da-zA-Z~!@#$%^&*()_+{}|\":<>?`[\\];',.\\/]{8,32}$"
	IDCardRege = "(\\d{17}[0-9a-zA-Z])|(\\d{14}[0-9a-zA-Z])"

	//PassRege = "^(?![0-9a-zA-Z]+$)(?![a-zA-Z!@#$%^&*]+$)(?![0-9!@#$%^&*]+$)[0-9A-Za-z!@#$%^&*]{8,16}$"

)

type UserController struct {
	CommonController
}

func (nc *UserController) Login() {
	req := common.LoginReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("LoginReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	//判断输入的是用户名还是手机号
	isPhoneNum, err := util.MatchStr(util.PhoneNumRege, req.Account)
	if err != nil {
		logger.Error("Login MatchPhoneStr error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}

	var userInfo = new(common.LoginUserInfo)
	if isPhoneNum {
		userInfo, err = service.GetUserInfoByPhoneNum(req.Account)
		// 查询数据库出错
	} else {
		userInfo, err = service.GetLoginUserInfo(req.Account)
	}

	// 查询数据库出错
	if err != nil {
		logger.Error("Login get login user information error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if err := nc.checkLocked(userInfo); err != nil {
		//日志和返回已经在函数中处理了直接返回
		return
	}
	if err := nc.checkPwd(userInfo, req.Password); err != nil {
		return
	}
	err = nc.affairsDoneAfterLogin(userInfo)
	if err != nil {
		return //已经处理
	}
	return
}

func (nc *UserController) checkLocked(userInfo *common.LoginUserInfo) error {
	//没找到用户名相关信息，这里需要到redis中缓存错误信息，避免账号泄露
	if userInfo == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名或密码错误"))
		return errors.New("password or username incorrect")
	}
	if userInfo.Locked {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("账号已被锁定，请联系管理员解锁"))
		return errors.New("user is locked")
	}
	if userInfo.FailTimes >= 5 {
		remainTime := userInfo.LockedTime.Add(15 * time.Minute).Sub(time.Now())
		if remainTime > 0 {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessagef("当前用户已被锁定，请%d分钟后重试", int(remainTime.Minutes())))
			return errors.New("user is locked")
		}
	}
	return nil
}

func (nc *UserController) checkPwd(userInfo *common.LoginUserInfo, reqPassword string) error {
	// 开始校验密码
	inPasswd, err := util.EncryptWithSalt(reqPassword, userInfo.Salt, common.Iter, common.KeyLength)
	if err != nil {
		logger.Error("EncryptWithSalt error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return errors.Errorf("EncryptWithSalt error: %v", err)
	}
	if inPasswd != userInfo.Password {
		times := userInfo.FailTimes%5 + 1
		curTimes := userInfo.FailTimes + 1
		if err := service.UpdateFailTimes(userInfo.Id, curTimes); err != nil {
			logger.Error("Login error: %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return errors.Errorf("Login error: %v", err)
		}
		// 连续失败5次锁定账户
		if times == 5 {
			// 更新锁定时间
			if err := service.UpdateLockedTime(userInfo.Id, time.Now()); err != nil {
				logger.Error("Login error: %v", err)
				nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
				return errors.Errorf("Login error: %v", err)
			}
			// 小于20次提示，请15min后重试
			if curTimes < 20 {
				nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("当前用户已被锁定，请15分钟后重试"))
				return errors.New("user locked")
			}

			// 锁定账户
			if _, err := service.UpdateLockedById(userInfo.Id, true); err != nil {
				logger.Error("Login error: %v", err)
				nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
				return errors.Errorf("Login error: %v", err)
			}
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("当前用户失败登录次数过多，请联系管理员解锁"))
			return errors.New("too much login fail times")
		} else {
			//返回还可以尝试的次数
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessagef("账号或密码错误，再错误%d次后账号将会锁定15分钟", 5-times))
			return errors.New("username or password incorrect")
		}
	}
	return nil
}

// checkPhoneAndVerificationCodeValid 判断手机号和手机验证码是否合法
func (nc *UserController) checkPhoneAndVerificationCodeValid(phoneNum, verificationCode string) error {
	exists, valid, err := util.JudgeInputPhoneAndVerValid(phoneNum, verificationCode)
	if err != nil {
		logger.Error("LoginUsingPhoneNum JudgeInputPhoneAndVerValid error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return errcode.E.Base.RequestInvalid.WithErr(err)
	}
	if (!exists) || (!valid) {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效的手机号和验证码"))
		return errcode.E.Base.ErrorMessage.WithMessage("请输入有效的手机号和验证码")
	}
	matching, vCodeExists, err := service.CompareVerificationCodeInRedis(phoneNum, verificationCode, db.RedisPool)
	if err != nil {
		logger.Error("LoginUsingPhoneNum Compare Verification Code In Redis error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return errcode.E.Internal.Internal.WithErr(err)
	}
	if !vCodeExists {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("验证码未获取或已失效，请先获取验证码"))
		return errcode.E.Base.ErrorMessage.WithMessage("验证码未获取或已失效，请先获取验证码")
	}
	if !matching {
		err = service.UpdatePhoneVerifyFailTimes(phoneNum, db.RedisPool)
		if err != nil {
			logger.Error("LoginUsingPhoneNum update PhoneVerifyFailTimes error: %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return errcode.E.Internal.Internal.WithErr(err)
		}
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("验证码错误"))
		return errcode.E.Base.ErrorMessage.WithMessage("验证码错误")
	}
	return nil
}

// LoginUsingPhoneNum 手机号登录
func (nc *UserController) LoginUsingPhoneNum() {
	req := common.LoginByPhoneReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("LoginReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	if err := nc.checkPhoneAndVerificationCodeValid(req.PhoneNum, req.VerificationCode); err != nil {
		return //错误已经返回处理
	}

	userInfo, err := service.GetUserInfoByPhoneNum(req.PhoneNum)
	// 查询数据库出错
	if err != nil {
		logger.Error("LoginUsingPhoneNum get login user information error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if userInfo == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("该手机号还未绑定用户，请先绑定"))
		return
	}

	if err := nc.checkLocked(userInfo); err != nil {
		//日志和返回已经在函数中处理了直接返回
		return
	}
	err = service.DeletePhoneVerCodeInRedis(req.PhoneNum, db.RedisPool)
	if err != nil {
		logger.Error("LoginUsingPhoneNum delete phone verification code error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
	}
	err = nc.affairsDoneAfterLogin(userInfo)
	if err != nil { // 已经处理
		return
	}
	return
}

func (nc *UserController) CheckUserRegPhone() {
	userIdStr := nc.Ctx.Input.Query("userId")
	if len(userIdStr) == 0 {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户ID为空"))
		return
	}
	userId := cast.ToInt(userIdStr)
	regPhone, err := service.CheckPhoneNumRegByID(userId)
	if err != nil {
		logger.Error("checkUserRegPhone error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("无法查询用户信息"))
		return
	}
	res := &common.CheckPhoneRegResp{
		PhoneReg: regPhone,
	}
	nc.HandleBusinessObj(res)
}

// affairsDoneAfterLogin 登录后系统需要做的操作
func (nc *UserController) affairsDoneAfterLogin(userInfo *common.LoginUserInfo) error {
	// 登录成功重置连续失败登录次数
	if err := service.UpdateFailTimes(userInfo.Id, 0); err != nil {
		logger.Error("Login error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return err
	}
	//在session中存入userId和userName和role
	nc.SetSession(common.SessionUserId, userInfo.Id)
	nc.SetSession(common.SessionUserName, userInfo.Name)
	nc.SetSession(common.SessionRole, userInfo.Role)
	nc.SetSession(common.SessionPwdChanged, userInfo.PwdChanged)

	lifeTime, err := beego.AppConfig.Int("jwtTokenLifeTimeMinute")
	if err != nil {
		logger.Error("read app config error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("read app config error"))
		return err
	}
	expiredTime := time.Now().Add(time.Minute * time.Duration(lifeTime))
	token, err := util.GetJWTToken(nc.Ctx.Input.CruSession.SessionID(), expiredTime)
	if err != nil {
		logger.Error("GetJWTToken error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage("GetJWTToken error"))
		return err
	}
	res := &common.LoginResp{
		UserName:      userInfo.Name,
		UserId:        userInfo.Id,
		Role:          userInfo.Role,
		PwdChanged:    userInfo.PwdChanged,
		Authenticated: userInfo.Authenticated,
	}
	nc.Ctx.Output.Header("newToken", token)
	nc.Ctx.Output.Header("tokenExpiredTime", cast.ToString(expiredTime.UnixMilli()))
	nc.HandleBusinessObj(res)
	return nil
}

// checkUserNameAndPwdValid 辅助函数判断用户名和密码是否合法
func (nc *UserController) checkUserNameAndPwdValid(name, password string) error {
	nameCheck, err := util.MatchStr(NameRege, name)
	if err != nil {
		logger.Error("username regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return err
	}
	if !nameCheck {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名取值范围：1到 32 个字符组成，包括字母、数字、下划线 (_)、连字符 (-) 和句点 (.), 且必须包含字母"))
		return errcode.E.Base.ErrorMessage.WithMessage("用户名取值范围：1到 32 个字符组成，包括字母、数字、下划线 (_)、连字符 (-) 和句点 (.), 且必须包含字母")
	}
	passCheck, err := util.MatchStr(PassRege, password)
	if err != nil {
		logger.Error("password regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return errcode.E.Internal.Internal.WithErr(err)
	}
	if !passCheck {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码至少包含大小写字母、数字和特殊字符（~!@#$%^&*()_+{}|\":<>?`[];',./）,且长度8-32"))
		return errcode.E.Base.ErrorMessage.WithMessage("密码至少包含大小写字母、数字和特殊字符（~!@#$%^&*()_+{}|\":<>?`[];',./）,且长度8-32")
	}
	return nil
}
func (nc *UserController) Registration() {
	req := common.RegistrationReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("LoginReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	//判断图形验证码
	if err := nc.checkCaptcha(req.VerificationCode); err != nil {
		return
	}
	// 检查用户名和密码正则
	if err := nc.checkUserNameAndPwdValid(req.Name, req.Password); err != nil {
		return //已处理
	}
	// 检查是否重名
	userInfo, err := service.GetLoginUserInfo(req.Name)
	if err != nil {
		logger.Error("Registration error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if userInfo != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名已存在"))
		return
	}

	needUpdatePhone, err := nc.checkPhoneNumCanBeUsed(req)
	if err != nil {
		return //错误已经在下面函数处理
	}
	resp := common.RegistrationResp{UserName: req.Name, PhoneNumber: ""}
	if needUpdatePhone {
		err = service.CreatUser(req.Name, req.Password, req.PhoneNumber)
		resp.PhoneNumber = req.PhoneNumber
	} else {
		err = service.CreatUser(req.Name, req.Password, "")
	}
	if err != nil {
		logger.Error("create new user error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	userInfo, err = service.GetLoginUserInfo(req.Name)
	if err != nil {
		logger.Error("Registration error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	err = nc.affairsDoneAfterLogin(userInfo)
	if err != nil {
		return
	}
	return
}

// checkPhoneExistsInRegistrationReq 判断注册请求是否带了手机号， 是否合法， 数据库中是否存在
func (nc *UserController) checkPhoneExistsInRegistrationReq(phoneNumber, phoneVerificationCode string) (bool, error) {
	phoneExists, phoneValid, err := util.JudgeInputPhoneAndVerValid(phoneNumber, phoneVerificationCode)
	if err != nil {
		logger.Error("create new user error at JudgeInputPhoneAndVerValid: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return false, err
	}
	if !phoneValid {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("手机号或验证码不符合输入要求"))
		return false, errcode.E.Base.ErrorMessage.WithMessage("手机号或验证码不符合输入要求")
	}
	if phoneExists {
		phoneUsed, err := service.CheckPhoneNumUsed(phoneNumber)
		if err != nil {
			logger.Error("Verify Phone check error: %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return false, err
		}
		if phoneUsed {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("手机号已被使用"))
			return false, errcode.E.Base.ErrorMessage.WithMessage("手机号已被使用")
		}
	}
	return phoneExists, nil
}

func (nc *UserController) UpdatePhoneNum() {
	req := common.UpdatePhoneNumReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("UpdatePhoneNum Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	//判断图形验证码
	phoneExistsInRegReq, err := nc.checkPhoneExistsInRegistrationReq(req.PhoneNumber, req.PhoneVerificationCode)
	if err != nil {
		return //错误已处理
	}
	if !phoneExistsInRegReq {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效手机号"))
		return
	}
	verCodeMatching, verCodeExists, err := service.CompareVerificationCodeInRedis(req.PhoneNumber, req.PhoneVerificationCode, db.RedisPool)
	if err != nil {
		logger.Error("CheckPhoneVerificationCode: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if !verCodeExists {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("还未申请验证码或验证码已失效"))
		return
	}
	if !verCodeMatching {
		err = service.UpdatePhoneVerifyFailTimes(req.PhoneNumber, db.RedisPool)
		if err != nil {
			logger.Error("Fail to update Phone Verify Fail Times %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return
		}
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("验证码错误！"))
		return
	}
	//用完就删
	err = service.DeletePhoneVerCodeInRedis(req.PhoneNumber, db.RedisPool)
	if err != nil {
		logger.Error("Fail to delete verCode in Redis: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	err = service.UpdatePhoneNumber(req.UserId, req.PhoneNumber)
	if err != nil {
		logger.Error("UpdatePhoneNum: Fail to update phoneNumber in DB: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.HandleBusinessSuccess()

}

// checkPhoneNumCanBeUsed 验证手机号
func (nc *UserController) checkPhoneNumCanBeUsed(req common.RegistrationReq) (bool, error) {
	needUpdatePhone := false
	phoneExistsInRegReq, err := nc.checkPhoneExistsInRegistrationReq(req.PhoneNumber, req.PhoneVerificationCode)
	if err != nil {
		return false, err //错误已处理
	}
	if phoneExistsInRegReq { //存在且合法
		verCodeMatching, verCodeExists, err := service.CompareVerificationCodeInRedis(req.PhoneNumber, req.PhoneVerificationCode, db.RedisPool)
		if err != nil {
			logger.Error("CheckPhoneVerificationCode: %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return false, err
		}
		if !verCodeExists {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("还未申请验证码或验证码已失效"))
			return false, errcode.E.Base.ErrorMessage.WithMessage("还未申请验证码或验证码已失效")
		}
		if !verCodeMatching {
			err = service.UpdatePhoneVerifyFailTimes(req.PhoneNumber, db.RedisPool)
			if err != nil {
				logger.Error("Fail to update Phone Verify Fail Times %v", err)
				nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
				return false, err
			}
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("验证码错误！"))
			return false, errcode.E.Base.ErrorMessage.WithMessage("验证码错误！")
		}
		needUpdatePhone = true
		//用完就删
		err = service.DeletePhoneVerCodeInRedis(req.PhoneNumber, db.RedisPool)
		if err != nil {
			logger.Error("Fail to delete verCode in Redis: %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
			return true, errcode.E.Internal.Internal.WithErr(err)
		}
	}
	return needUpdatePhone, nil
}

func (nc *UserController) checkCaptcha(verificationCode string) error {
	captchaSession := nc.GetSession(common.SessionCaptchaName)
	if captchaSession == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请先获取图形验证码"))
		return errors.New("have not get captcha")
	}
	captcha, ok := captchaSession.(*common.Captcha)
	if !ok {
		logger.Error("Registration Assert_Error.")
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return errors.New("Registration Assert_Error.")
	}
	// 判断验证码是否使用过
	if captcha.Used {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请勿重复使用图形验证码，请重新获取图形验证码"))
		return errors.New("already used captcha")
	}
	// 验证码是否过期
	if time.Now().After(captcha.CreateDate.Add(5 * time.Minute)) {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("图形验证码已过期，请重新获取图形验证码"))
		return errors.New("old captcha")
	}
	if captcha.Content != strings.ToLower(verificationCode) {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("图形验证码错误"))
		captcha.Used = true
		nc.SetSession(common.SessionCaptchaName, captcha)
		return errors.New("incorrect captcha")
	}
	captcha.Used = true
	nc.SetSession(common.SessionCaptchaName, captcha)
	return nil
}

func (nc *UserController) Logout() {
	// 设置token的过期时间为现在
	newToken, err := util.GetJWTToken(nc.Ctx.Input.CruSession.SessionID(), time.Now())
	if err != nil {
		logger.Error("Logout error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.Ctx.Output.Header("newToken", newToken)
	nc.DestroySession()
	nc.HandleBusinessSuccess()
}

func (nc *UserController) GetCaptcha() {
	sessionCaptcha := nc.GetSession(common.SessionCaptchaName)
	captcha := &common.Captcha{}
	if sessionCaptcha == nil {
		captcha = &common.Captcha{
			Content:      "",
			CreateDate:   time.Now(),
			RequestTimes: 0,
			Used:         false,
		}
	} else {
		var ok bool
		captcha, ok = sessionCaptcha.(*common.Captcha)
		if !ok {
			logger.Error("GetCaptcha Assert_Error.")
			nc.HandleErrCode(errcode.E.Internal.Internal)
			return
		}
	}

	// 检查请求请求次数，如果大于10 返回错误信息
	if captcha.RequestTimes >= 30 {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请求图形验证码过于频繁，请稍后再试"))
		return
	}
	// 生成验证码，
	str := util.GetRandStr(4)
	img := util.ImgText(75, 36, str)
	base64Img := base64.StdEncoding.EncodeToString(img)
	captcha.Content = strings.ToLower(str)
	captcha.RequestTimes++
	captcha.CreateDate = time.Now()
	captcha.Used = false
	nc.SetSession(common.SessionCaptchaName, captcha)
	nc.HandleBusinessObj(base64Img)

}

func (nc *UserController) Profile() {

	userName := nc.GetSession(common.SessionUserName)
	if userName == nil {
		logger.Error("Failed to get user name from session id")
		nc.HandleErrCode(errcode.E.Certification.HavaNotLogin)
		return
	}
	userNameStr, ok := userName.(string)
	if !ok {
		logger.Error("Profile Assert_Error.")
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}
	res, err := service.GetUserProfile(userNameStr)
	if err != nil {
		logger.Error("get userProfile error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.HandleBusinessObj(res)
}

// Unsubscribe 用户注销
func (nc *UserController) Unsubscribe() {
	req := common.UnsubscribeReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("UnsubscribeReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	userInfo := nc.getUserInfoFromSession()
	if userInfo == nil {
		//错误已经在之前的函数中处理
		return
	}
	if userInfo.Role == service.AdminRoleName {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("管理员账号不能注销！"))
		return
	}
	// 密码二次校验
	if err := nc.unsubscribePwdCheckHelper(userInfo, req.Password); err != nil {
		return
	}

	sessionUserId := nc.GetSession(common.SessionUserId)
	if sessionUserId == nil {
		logger.Error("Failed to get user id from session id")
		nc.HandleErrCode(errcode.E.Certification.HavaNotLogin)
		return
	}
	userId, ok := sessionUserId.(int)
	if !ok {
		logger.Error("unsubscribe Assert_Error.")
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}

	err := service.Unsubscribe(userId)
	if err != nil {
		logger.Error("unsubscribe error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	// 设置token的过期时间为现在
	newToken, err := util.GetJWTToken(nc.Ctx.Input.CruSession.SessionID(), time.Now())
	if err != nil {
		logger.Error("Logout error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	nc.Ctx.Output.Header("newToken", newToken)
	nc.DestroySession()
	nc.HandleBusinessSuccess()
}

//辅助函数 用于通过session中的用户名去获得userinfo
func (nc *UserController) getUserInfoFromSession() *common.LoginUserInfo {
	userNameSession := nc.GetSession(common.SessionUserName)
	if userNameSession == nil {
		logger.Error("Failed to get user name from session id")
		nc.HandleErrCode(errcode.E.Certification.HavaNotLogin)
		return nil
	}
	userName, ok := userNameSession.(string)
	if !ok {
		logger.Error("Unsubscribe Assert_Error.")
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return nil
	}
	userInfo, err := service.GetLoginUserInfo(userName)
	if err != nil {
		logger.Error("get LoginUserInfo error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return nil
	}
	if err != nil {
		logger.Error("get LoginUserInfo error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return nil
	}
	return userInfo
}

// 辅助函数, 用于注销时去验证密码
func (nc *UserController) unsubscribePwdCheckHelper(userInfo *common.LoginUserInfo, reqPassword string) error {
	encryptPass, err := util.EncryptWithSalt(reqPassword, userInfo.Salt, common.Iter, common.KeyLength)
	if err != nil {
		logger.Error("get LoginUserInfo error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return errors.Errorf("get LoginUserInfo error: %v", err)
	}
	if encryptPass != userInfo.Password {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码错误，注销用户失败！"))
		return errors.New("incorrect password")
	}
	return nil
}

func (nc *UserController) ChangePass() {
	req := common.ChangePassReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("ChangePass Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	//验证原来密码是否正确
	userInfo := nc.getUserInfoFromSession()
	if userInfo == nil {
		//错误已经在之前的函数中处理
		return
	}
	encryptPass, err := util.EncryptWithSalt(req.OldPass, userInfo.Salt, common.Iter, common.KeyLength)
	if err != nil {
		logger.Error("get LoginUserInfo error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}
	if encryptPass != userInfo.Password {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码错误，更改密码失败！"))
		return
	}
	// 检查密码正则
	passCheck, err := util.MatchStr(PassRege, req.NewPass)
	if err != nil {
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if !passCheck {
		// 提示信息需要再细化
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("密码至少包含大小写字母、数字和特殊字符（~!@#$%^&*()_+{}|\":<>?\\`\\[];',./）,且长度8-32"))
		return
	}
	// 开始修改密码
	salt, encryptedPassword, err := util.EncryptPassword(req.NewPass, common.Iter, common.KeyLength)
	err = service.UpdatePassword(userInfo.Id, encryptedPassword, salt)
	if err != nil {
		logger.Error("ChangePass error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}
	// 更改session
	nc.SetSession(common.SessionPwdChanged, true)
	nc.HandleBusinessSuccess()
}

func (nc *UserController) CheckUserName() {
	userName := nc.Ctx.Input.Query("username")
	if len(userName) == 0 {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名为空"))
		return
	}
	userInfo, err := service.GetLoginUserInfo(userName)
	if err != nil {
		logger.Error("CheckUserName error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if userInfo != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户名已存在"))
		return
	}
	nc.HandleBusinessSuccess()
}

// CheckAuthentication 返回前端用户是否实名的结果
func (nc *UserController) CheckAuthentication() {

	UserId := cast.ToInt(nc.Ctx.Input.Query("userid"))
	authenticated, err := service.FindAuthenticationInfo(UserId)
	if err != nil {
		logger.Error("Check Authentication status error: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法通过user id查找到用户"))
		return
	}
	if authenticated {
		nc.HandleBusinessObj(true)
		return
	} else {
		nc.HandleBusinessObj(false)
		return
	}
}

// UserAuthentication 人证核身功能
func (nc *UserController) UserAuthentication() {
	req := common.RealNameAuthenticationReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("User Authentication Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}

	//通过前端传来的userID去查用户数据是否存在,然后根据userID去判断是否已经实名,若已实名则返回
	userAuthenticatedInfo, err := service.FindAuthenticationInfo(req.UserId)
	if err != nil {
		logger.Error("User Authentication error: fail to find user by user id")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员"))
		return
	}
	if userAuthenticatedInfo {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户已实名认证,无需再次认证"))
		return
	}

	// 对输入进行校验
	if req.VerificationName == "" {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入要认证的姓名!"))
		return
	}
	idNumCheck, err := util.MatchStr(IDCardRege, req.VerificationID)
	if err != nil {
		logger.Error("User Authentication error:idNum regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if !idNumCheck {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效的身份证号!"))
		return
	}

	err = nc.CheckIVSForUserAuthentication(req)
	if err != nil {
		return
	}
	//身份验证成功的情况

	//1.更新数据库
	err = service.UpdateAuthentication(req.UserId, true)
	if err != nil {
		logger.Error("Update authentication info error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}
	nc.HandleBusinessSuccess()
}
func (nc *UserController) CheckIVSForUserAuthentication(req common.RealNameAuthenticationReq) error {
	// 调用IVS服务
	userAuthenticated, err := util.CallIVSService(req.VerificationName, req.VerificationID)

	//判断调用是否成功
	if err != nil {
		logger.Error("Authentication failed: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("认证过程出现错误,请稍后重试"))
		return err
	}

	if userAuthenticated.Code != util.AuthenticationSuccessCode {
		err = errors.Errorf("认证失败")
		errCode := util.ParserAuthenticationErrorCode(userAuthenticated.Code)
		nc.HandleErrCode(errCode)
		return err
	}
	return nil
}

// VerifyPhoneNum 发送手机验证码
func (nc *UserController) VerifyPhoneNum() {
	req := common.VerifyPhoneNumReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("VerifyPhoneNumReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	phoneCheck, err := util.MatchStr(util.PhoneNumRege, req.PhoneNum)
	if err != nil {
		logger.Error("phone number regexp match error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if !phoneCheck {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请输入有效的手机号码"))
		return
	}
	verInfo, err := service.GetPhoneVerificationFromRedis(req.PhoneNum, db.RedisPool)
	if err != nil {
		logger.Error("GetPhoneVerificationFromRedis failed: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	if verInfo != nil {
		currentTimeUnix := time.Now().UnixMilli()
		if currentTimeUnix < verInfo.CdTime {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("请等待一分钟之后再发送短信验证码"))
			return
		}
	}
	phoneUsed, err := service.CheckPhoneNumUsed(req.PhoneNum)
	if err != nil {
		logger.Error("Verify Phone check error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	//排除掉 要求注册但手机号已注册的情况 和 要求登录但手机号未使用的情况
	if req.IsReg && phoneUsed {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("该手机号已被注册"))
		return
	}
	if !req.IsReg && !phoneUsed {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("该手机号未被注册"))
		return
	}
	err = nc.setAndSendVerificationCode(req.PhoneNum)
	if err != nil {
		return //已经在下面函中处理
	}
	return
}

// 产生并发送验证码, 同时写入到redis
func (nc *UserController) setAndSendVerificationCode(phoneNum string) error {
	verificationCode := util.GenerateVerificationCode(util.VerCodeLength)
	err := service.SendVerificationCodeBySMS(phoneNum, verificationCode)
	if err != nil {
		logger.Error("SendVerificationCodeBySMS failed: fail to send SMS")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("发送短信失败"))
		return err
	}
	currentTime := time.Now()
	cdTime := currentTime.Add(util.VerCodeCooldownTimeMinute * time.Minute)
	expireTime := currentTime.Add(util.VerCodeExpireTimeMinute * time.Minute)
	newVerInfo := &common.PhoneVerificationInfo{
		Code:       verificationCode,
		CdTime:     cdTime.UnixMilli(),
		ExpireTime: expireTime.UnixMilli(),
		FailTimes:  0,
	}

	err = service.WritePhoneVerCodeIntoRedis(phoneNum,
		newVerInfo, db.RedisPool)
	if err != nil {
		logger.Error("Update Phone Number error: Phone number update failed")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("保存手机号失败"))
		return err
	}
	resp := common.PhoneVerifyResp{
		CooldownTime: cdTime.UnixMilli(),
	}
	nc.HandleBusinessObj(resp)
	return nil
}

func (nc *UserController) UpdateProfilePhoto() {
	file, information, err := nc.GetFile("file")
	if err != nil {
		logger.Error("read file error: %v", err)
		nc.HandleErrCode(errcode.E.File.SaveFailed.WithErr(err))
		return
	}

	// 读取上传文件内容, data就是文件的内容
	// ioutil.ReadAll返回[]byte类型数据
	data, err := ioutil.ReadAll(file)

	defer file.Close()

	fileType := ""
	fileExt := filepath.Ext(information.Filename)
	if fileExt == ".jpg" || fileExt == ".png" || fileExt == ".gif" || fileExt == ".jpeg" || fileExt == ".JPG" || fileExt == ".PNG" || fileExt == ".GIF" || fileExt == ".JPEG" {
		fileType = ".png"
	} else {
		logger.Error("SaveFile error: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("仅支持jpg,png,gif,jpeg格式"))
		return
	}

	obsKey, err := service.SaveFile(data, fileType)

	if err != nil {
		logger.Error("SaveFile error: %v", err)
		nc.HandleErrCode(errcode.E.File.SaveFailed.WithErr(err))
	}

	userId, err := nc.GetInt("userId")
	if err != nil {
		logger.Error("UpdateProfilePhoto : GetInt error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}
	err = service.UpdateProfilePhoto(userId, obsKey)
	if err != nil {
		logger.Error("UpdateProfilePhoto : UpdateProfilePhoto error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithErr(err))
		return
	}

	profilePhotoUrl, err := util.GetAssetUrl(obsKey)
	if err != nil {
		logger.Error("UpdateProfilePhoto: GetAssetUrl error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}
	response := &common.FileResponse{}
	response.ObsKey = profilePhotoUrl
	nc.HandleBusinessObj(response)
}

func (nc *UserController) GetProfilePhoto() {
	userId := cast.ToInt(nc.Ctx.Input.Query("userId"))
	user, err := service.GetUserAllInfoById(userId)
	if err != nil {
		logger.Error("GetProfilePhoto: GetUserAllInfoById: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal)
		return
	}
	if user == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("未找到用户"))
		return
	}
	profilePhotoUrl := ""
	if user.ProfilePhotoUrl != "" {
		profilePhotoUrl, err = util.GetAssetUrl(user.ProfilePhotoUrl)
		if err != nil {
			logger.Error("GetProfilePhoto: GetAssetUrl error: %v", err)
			nc.HandleErrCode(errcode.E.Internal.Internal)
			return
		}
	}
	resp := common.GetProfilePhoto{
		ProfilePhotoUrl: profilePhotoUrl,
	}
	nc.HandleBusinessObj(resp)
}
